#include "SpaceFortress.h"

#include "BlueprintManager.h"
#include "../Stringhash/Stringhash.h"

#include "Json.h"
#include "FileManager.h"
#include <assert.h>

BlueprintManager::InstanceData::InstanceData()
{
	size = 0;
	capacity = 0;
	buffer = NULL;

	blueprint_id = NULL;
	comodity_hash = NULL;
	comodity_index = NULL;
	rate = NULL;
}

BlueprintManager::BlueprintData::BlueprintData()
{
	size = 0;
	capacity = 0;
	buffer = NULL;

	input_start = NULL;
	input_size = NULL;
	output_start = NULL;
	output_size = NULL;
	//output_index = NULL;
	tool_index = NULL;
}

BlueprintManager::BlueprintManager()
{

}

BlueprintManager::~BlueprintManager()
{
	delete[] _input_data.buffer;
	delete[] _output_data.buffer;
	delete[] _blueprint_data.buffer;
}

void BlueprintManager::AllocateInput(const uint32 set_capacity)
{
	assert(set_capacity > capacity);

	InstanceData new_data;
	const uint32 bytes = set_capacity * (3 * sizeof(uint32) + sizeof(float));
	new_data.buffer = new unsigned char[bytes];
	new_data.size = _input_data.size;
	new_data.capacity = set_capacity;

	new_data.blueprint_id = (uint32*)(new_data.buffer);
	new_data.comodity_hash = (uint32*)(new_data.blueprint_id + set_capacity);
	new_data.comodity_index = (uint32*)(new_data.comodity_hash + set_capacity);
	new_data.rate = (float*)(new_data.comodity_index + set_capacity);

	memcpy(new_data.blueprint_id, _input_data.blueprint_id, _input_data.size * sizeof(uint32));
	memcpy(new_data.comodity_hash, _input_data.comodity_hash, _input_data.size * sizeof(uint32));
	memcpy(new_data.comodity_index, _input_data.comodity_index, _input_data.size * sizeof(uint32));
	memcpy(new_data.rate, _input_data.rate, _input_data.size * sizeof(float));

	delete[] _input_data.buffer;
	_input_data = new_data;
}

void BlueprintManager::AllocateOutput(const uint32 set_capacity)
{
	assert(set_capacity > capacity);

	InstanceData new_data;
	const uint32 bytes = set_capacity * (3 * sizeof(uint32) + sizeof(float));
	new_data.buffer = new unsigned char[bytes];
	new_data.size = _output_data.size;
	new_data.capacity = set_capacity;

	new_data.blueprint_id = (uint32*)(new_data.buffer);
	new_data.comodity_hash = (uint32*)(new_data.blueprint_id + set_capacity);
	new_data.comodity_index = (uint32*)(new_data.comodity_hash + set_capacity);
	new_data.rate = (float*)(new_data.comodity_index + set_capacity);

	memcpy(new_data.blueprint_id, _output_data.blueprint_id, _output_data.size * sizeof(uint32));
	memcpy(new_data.comodity_hash, _output_data.comodity_hash, _output_data.size * sizeof(uint32));
	memcpy(new_data.comodity_index, _output_data.comodity_index, _output_data.size * sizeof(uint32));
	memcpy(new_data.rate, _output_data.rate, _output_data.size * sizeof(float));

	delete[] _output_data.buffer;
	_output_data = new_data;
}

void BlueprintManager::AllocateBlueprint(const uint32 set_capacity)
{
	assert(set_capacity > capacity);

	BlueprintData new_data;
	const uint32 bytes = set_capacity * (5 * sizeof(uint32));
	new_data.buffer = new unsigned char[bytes];
	new_data.size = _blueprint_data.size;
	new_data.capacity = set_capacity;

	new_data.input_start = (uint32*)(new_data.buffer);
	new_data.input_size = (uint32*)(new_data.input_start + set_capacity);
	new_data.output_start = (uint32*)(new_data.input_size + set_capacity);
	new_data.output_size = (uint32*)(new_data.output_start + set_capacity);
	new_data.tool_index = (uint32*)(new_data.output_size + set_capacity);

	memcpy(new_data.input_start, _blueprint_data.input_start, _blueprint_data.size * sizeof(uint32));
	memcpy(new_data.input_size, _blueprint_data.input_size, _blueprint_data.size * sizeof(uint32));
	memcpy(new_data.output_start, _blueprint_data.output_start, _blueprint_data.size * sizeof(uint32));
	memcpy(new_data.output_size, _blueprint_data.output_size, _blueprint_data.size * sizeof(uint32));
	memcpy(new_data.tool_index, _blueprint_data.tool_index, _blueprint_data.size * sizeof(uint32));

	delete[] _blueprint_data.buffer;
	_blueprint_data = new_data;
}

const OpenVector<uint32>& BlueprintManager::Initialize()
{
	// An array of all the string hashes of the string hashes for each comodity
	if (_input_data.size && _output_data.size && _blueprint_data.size) {
		return hash_vector;
	}

	// Load all the blueprints from json
	FString full_path = FPaths::GameContentDir() + "Blueprints.txt";
	FString LoadGameStringData;
	TSharedPtr<FJsonObject> data;
	data = MakeShareable(new FJsonObject());
	if (!FFileHelper::LoadFileToString(LoadGameStringData, *full_path)) {
		GEngine->AddOnScreenDebugMessage(-1, 5.0, FColor::Green, "Failed to load blueprints file!");
		return hash_vector;
	}

	TSharedRef< TJsonReader<TCHAR> > JsonReader = TJsonReaderFactory<TCHAR>::Create(LoadGameStringData);
	if (!FJsonSerializer::Deserialize(JsonReader, data)) {
		GEngine->AddOnScreenDebugMessage(-1, 5.0, FColor::Green, "Failed to de-serialize blueprint json data!");
		return hash_vector;
	}

	if (!data.IsValid()) {
		GEngine->AddOnScreenDebugMessage(-1, 5.0, FColor::Green, "Json object empty!");
		return hash_vector;
	}

	// this is the root of the file.
	TArray<TSharedPtr<FJsonValue>> root = data->GetArrayField("blueprints");
	for (int32 i = 0; i < root.Num(); i++) {
		TSharedPtr<FJsonObject> blueprint = root[i]->AsObject();
		TSharedPtr<FJsonObject> info = blueprint->GetObjectField("info");

		// Get the blueprint information
		FString name = info->GetStringField("name");
		Stringhash blueprint_hash(name);

		uint32 outputs = (uint32)info->GetNumberField("outputs");
		AllocateOutput(_output_data.capacity + outputs);

		uint32 inputs = (uint32)info->GetNumberField("inputs");
		AllocateInput(_input_data.capacity + inputs);

		// Initialize memory
		AllocateBlueprint(_blueprint_data.capacity + 1);
		const uint32 index = _blueprint_data.size++;

		// Assign the blueprint information
		_blueprint_data.input_start[index] = _input_data.size;
		_blueprint_data.input_size[index] = inputs;
		_blueprint_data.output_start[index] = _output_data.size;
		_blueprint_data.output_size[index] = outputs;


		// Get the components of the blueprint.
		TArray<TSharedPtr<FJsonValue>> components = blueprint->GetArrayField("components");
		for (int32 j = 0; j < components.Num(); j++) {
			TSharedPtr<FJsonObject> comodity = components[j]->AsObject();

			FString comodity_name = comodity->GetStringField("name");
			Stringhash comodity_hash(comodity_name);


			if (!comodity->GetStringField("type").Compare("output")) { // If its output
				const uint32 output_idx = _output_data.size++;
				_output_data.blueprint_id[output_idx] = blueprint_hash.hash;
				_output_data.comodity_hash[output_idx] = comodity_hash.hash;
				//_output_data.comodity_index this gets filled in later.
				_output_data.rate[output_idx] = comodity->GetNumberField("rate");

				// Outputs are unique to each blueprint. Each output is produced by only 1 blueprint.
				hash_vector.Add(comodity_hash.hash);
				_map.Insert(comodity_hash.hash, index); // map output hash to blueprint index that produces it.
			} else {
				const uint32 input_idx = _input_data.size++;
				_input_data.blueprint_id[input_idx] = blueprint_hash.hash;
				_input_data.comodity_hash[input_idx] = comodity_hash.hash;
				//_input_data.comodity_index this gets filled in later.
				_input_data.rate[input_idx] = comodity->GetNumberField("rate");
			}
		}
	}

	// This is probably a really clumsy way of doing all this.
	QuickSortAscend(hash_vector.data, 0, hash_vector.size);
	OpenMap<uint32, uint32> hash_to_index;
	for (uint32 i = 0; i < hash_vector.size; i++) {
		hash_to_index.Insert(hash_vector.data[i], i);
	}

	for (uint32 i = 0; i < _input_data.size; i++) {
		_input_data.comodity_index[i] = hash_to_index[_input_data.comodity_hash[i]];
	}

	for (uint32 i = 0; i < _output_data.size; i++) {
		_output_data.comodity_index[i] = hash_to_index[_output_data.comodity_hash[i]];
	}

	return hash_vector;
}

void BlueprintManager::GetBlueprintInput(const uint32 output_hash, uint32& input_start, uint32& input_count, float const* input_rates, uint32 const* input_indexes) const
{
	// first find the blueprint index. For now this is the output index aswell.
	const uint32 index = _map[output_hash];
	input_start = _blueprint_data.input_start[index];
	input_count = _blueprint_data.input_size[index];

	input_rates = &_input_data.rate[input_start];
	input_indexes = &_input_data.comodity_index[input_start];
}

void BlueprintManager::GetBlueprintOutput(const uint32 output_hash, uint32& output_start, uint32& output_count, float const* output_rates, uint32 const* output_indexes) const
{
	// first find the blueprint index. For now this is the output index aswell.
	const uint32 index = _map[output_hash];
	output_start = _blueprint_data.output_start[index];
	output_count = _blueprint_data.output_size[index];

	output_rates = &_output_data.rate[output_start];
	output_indexes = &_output_data.comodity_index[output_start];
}